
using Boilen.Primitives.CodeGeneration;
using Boilen.Primitives.Implementers;
using Boilen.Primitives.Members;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
using Xunit;
using Xunit.Extensions;


namespace Boilen.Primitives {

    public class TestPartialType {

        [Fact]
        public void constructor_Type_fails_for_null_arguments( ) {
            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( NullType ) );
        }

        [Fact]
        public void constructor_Name_Type_fails_for_null_arguments( ) {
            string name = "name";
            Type type = typeof( object );

            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( NullName, type ) );
            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( name, NullType ) );
        }

        [Fact]
        public void constructor_Type_BaseType_fails_for_null_arguments( ) {
            Type type = typeof( object );
            Type baseType = typeof( object );

            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( NullType, baseType ) );
            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( type, NullType ) );
        }

        [Fact]
        public void constructor_Type_BaseType_fails_for_same_Type( ) {
            Type type = typeof( object );
            Type baseType = typeof( object );

            Assert.Throws<ArgumentException>( ( ) => new PartialType( type, baseType ) );
        }

        [Fact]
        public void constructor_Type_BaseType_SilverlightBaseTypeName_fails_for_null_arguments( ) {
            Type type = typeof( Brush );
            Type baseType = typeof( Freezable );
            string silverlightBaseTypeName = typeof( FrameworkElement ).FullName;

            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( NullType, baseType, silverlightBaseTypeName ) );
            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( type, NullType, silverlightBaseTypeName ) );
            Assert.Throws<ArgumentNullException>( ( ) => new PartialType( type, baseType, NullName ) );
        }

        [Fact]
        public void constructor_Type_BaseType_SilverlightBaseTypeName_fails_for_same_Type( ) {
            Type type = typeof( object );
            Type baseType = typeof( object );
            string silverlightBaseTypeName = typeof( FrameworkElement ).FullName;

            Assert.Throws<ArgumentException>( ( ) => new PartialType( type, baseType, silverlightBaseTypeName ) );
        }


        [Theory]
        [InlineData( typeof( SourceClass ) )]
        [InlineData( typeof( SourceStruct ) )]
        public void compilation_fails_when_code_generation_fails( Type sourceType ) {
            CompilerResults compileResults;
            var pt = new PartialType( sourceType );
            pt.Implementers.Add( new FailingImplementer( pt ) );

            bool runResult = Compile.PartialType( pt, true, out compileResults );

            Assert.False( runResult );
            Assert.NotEmpty( compileResults.Errors );
        }

        [Theory]
        [InlineData( typeof( SourceClass ) )]
        [InlineData( typeof( SourceStruct ) )]
        public void Run_compiles_for_empty_Implementers( Type sourceType ) {
            var pt = new PartialType( sourceType );

            var members = Compile.PartialType( pt );

            Assert.InRange( members.Length, 0, 1 );
            if( members.Length > 0 ) {
                var constructor = Assert.IsAssignableFrom<ConstructorInfo>( members[0] );
                var parameters = constructor.GetParameters( );
                Assert.Empty( parameters );
            }
        }

        [Theory]
        [InlineData( typeof( SourceClass ) )]
        [InlineData( typeof( SourceStruct ) )]
        public void Run_compiles_for_customized_constructor( Type sourceType ) {
            var pt = new PartialType( sourceType )
                .SetConstructorAccessibility( Accessibility.Internal )
                .AddConstructorSuppressionAttribute( "Category", "ID", "Justification", false )
                .AddImmutableProperty<int>( "Name", "Description" )
                ;

            var members = Compile.PartialType( pt );
        }


        #region Utility

        private const Type NullType = null;
        private const string NullName = null;

        private sealed class FailingImplementer : Implementer<object> {
            public FailingImplementer( PartialType parent ) : base( parent, "name", "description" ) { }
            protected override IEnumerable<Member> GetMembers( ) {
                yield return new FailingMember( );
            }
        }

        private sealed class FailingMember : Member {
            public FailingMember( ) : base( "name" ) { }
            public override Extensions UsedExtensions { get { return Extensions.None; } }
            protected override void WriteCore( ICodeWriter writer ) {
                throw new NotImplementedException( );
            }
        }

        #endregion

    }

}
